package org.tulkas.ext;

import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.tulkas.base.ITulkasApplicationContext;
import org.tulkas.exception.SystemException;

/**
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
@Service
public class ExtensionManager {
	@Autowired
	private ITulkasApplicationContext appContext;

	@Autowired
	private PluginManager pluginMgr;

	private Map<String, List<ConfigExtension>> configExts = new HashMap<String, List<ConfigExtension>>();

	public void init() {
		Plugin[] plugins = pluginMgr.getAllPlugins();
		for (Plugin plugin : plugins) {
			ConfigExtension[] exts = plugin.getConfigExtensions();
			for (ConfigExtension ext : exts) {
				List<ConfigExtension> list = configExts.get(ext.getPointId());
				if (list == null) {
					list = new ArrayList<ConfigExtension>();
					configExts.put(ext.getPointId(), list);
				}
				list.add(ext);
			}
		}
	}

	public String[] getAllExtensionPointIds() {
		return appContext.getBeanNamesForType(IExtensionPoint.class);
	}

	public String[] getJavaExtensionPointIds() {
		return appContext.getBeanNamesForType(IJavaExtensionPoint.class);
	}

	public String[] getConfigExtensionPoints() {
		return appContext.getBeanNamesForType(IConfigExtensionPoint.class);
	}

	public IExtensionPoint getExtensionPoint(String extPointId) {
		return (IExtensionPoint) appContext.getBean(extPointId);
	}

	public IExtensionPoint getExtensionPoint(
			Class<? extends IExtensionPoint> extPointCls) {
		return (IExtensionPoint) appContext.getBean(extPointCls);
	}

	public String getExtensionPointId(IExtensionPoint extPoint) {
		String[] ids = appContext.getBeanNamesForType(extPoint.getClass());
		if (ids != null && ids.length > 0) {
			return ids[0];
		}
		return null;
	}

	public <T extends IExtension> IJavaExtensionPoint<T> getJavaExtensionPoint(
			Class<IJavaExtensionPoint<T>> extCls) {
		return (IJavaExtensionPoint<T>) appContext.getBean(extCls);
	}

	@SuppressWarnings("unchecked")
	public <T extends IExtension> Class<T> getJavaExtensionClass(
			IJavaExtensionPoint<T> extPoint) {
		Type[] gInters = extPoint.getClass().getGenericInterfaces();
		if (gInters != null && gInters.length > 0
				&& gInters[0] instanceof ParameterizedType) {
			ParameterizedType gInter = (ParameterizedType) gInters[0];
			Type[] types = gInter.getActualTypeArguments();
			if (types != null && types.length > 0
					&& types[0] instanceof Class<?>) {
				return (Class<T>) types[0];
			}
		}
		throw new SystemException("Cannot determine extension type for ["
				+ extPoint.getClass() + "]");
	}

	@SuppressWarnings("unchecked")
	public <T extends IExtension> T[] getJavaExtensions(
			IJavaExtensionPoint<T> extPoint) {
		T[] ret = null;
		Class<T> extCls = (Class<T>) getJavaExtensionClass(extPoint);
		Map<String, T> map = appContext.getBeansOfType(extCls);
		ret = (T[]) Array.newInstance(extCls, map.size());
		return map.values().toArray(ret);
	}

	public ConfigExtension[] getConfigExtensions(IConfigExtensionPoint extPoint) {
		String extPointId = getExtensionPointId(extPoint);
		List<ConfigExtension> list = configExts.get(extPointId);
		if (list != null) {
			return list.toArray(new ConfigExtension[list.size()]);
		} else {
			return new ConfigExtension[0];
		}
	}

}
